
#include <stdint.h>
#include <string.h>
#include "error_code.h"
#include "character_module.h"
#include "etu_timer.h"
#include "slot.h"
//	#include "led.h"

#define CMD_CLA		0
#define CMD_INS		1
#define CMD_P1		2
#define CMD_P2		3
#define CMD_P3		4

#define COMMAND_HEADER_SIZE 	5

#define SESSION_IN		0
#define SESSION_OUT		1
/*
typedef struct
{
    uint8_t *apdu_send;
    uint32_t apdu_send_offset;
    uint32_t Lc;
    uint8_t *apdu_receive;
    uint32_t apdu_receive_len;
    uint32_t Le;
} session_t;
*/
typedef struct
{
    uint8_t cmd[5];
    //uint8_t *cmd;
    uint8_t *data;
    uint32_t offset;
    uint32_t len;
    uint8_t sw[2];
    uint8_t dir;
    uint32_t (* transfer)(slot_t *, uint8_t *, uint32_t );
} session_t;


/*.................................................................*/

static uint32_t send_buffer(slot_t *slot, uint8_t buf[], uint32_t len)
{
    uint32_t ret = NO_ERR;
    uint32_t i;
    character_module_t *module = slot->module;
    icc_t *icc = slot->icc;
    uint32_t guard_time = icc->extra_guardtime;

    for(i = 0; i < (len - 1); i++)
    {
        ret = CharacterModule_SendByte(module, buf[i], guard_time);

        if(ret != NO_ERR)
        {
            break;
        }
    }

    if(ret == NO_ERR)
    {
        ret = CharacterModule_SendByte(module, buf[i], 0);
    }

    return ret;
}


static uint32_t receive_buffer(slot_t *slot, uint8_t buf[], uint32_t len)
{
    character_module_t *module = slot->module;
    icc_t *icc = slot->icc;
    return CharacterModule_ReceiveData(module, buf, len, icc->WWT);
}

static uint32_t receive_procedure_byte(slot_t *slot, uint8_t *procedure_byte)
{
    return receive_buffer(slot, procedure_byte, 1);
}

static uint32_t recieve_sw(slot_t *slot, uint8_t *sw)
{
    return receive_buffer(slot, sw, 1);;
}


static uint32_t transfer_out(slot_t *slot, uint8_t data[], uint32_t len)
{
    return send_buffer(slot, data, len);
}

static uint32_t transfer_in(slot_t *slot, uint8_t data[], uint32_t len)
{
    return receive_buffer(slot, data, len);
}


static uint32_t send_command_header(slot_t *slot, session_t *session)
{
    uint8_t *cmd = session->cmd;
    uint32_t cmd_len = COMMAND_HEADER_SIZE;

    return send_buffer(slot, cmd, cmd_len);
}


static uint32_t Session_Transmission(slot_t *slot, session_t *session)
{
    uint32_t ret;
    uint8_t INS = session->cmd[1];
    uint8_t procedure_byte = 0;

    //CharacterModule_SetMaxInterval(slot->module, WWT);
    ret = send_command_header(slot, session);

    if(ret != NO_ERR)
    {
        return ret;
    }

    do
    {
        ret = receive_procedure_byte(slot, &procedure_byte);

        if(ret != NO_ERR)
        {
            break;
        }


        if(procedure_byte == 0x60)
        {
            /*
            The TTL shall provide additional work waiting
            time as defined in this section .
            */
        }
        else if((procedure_byte & 0xF0) == 0x60 || (procedure_byte & 0xF0) == 0x90)
        {
            /*
            TTL shall wait for a further status byte
            */
            ret = recieve_sw(slot, &session->sw[1]);

            if(ret == NO_ERR)
            {
                session->sw[0] = procedure_byte;
            }

            break;
        }
        else if(procedure_byte == INS)
        {
            /*
            All remaining data bytes shall be transferred by
            the TTL, or the TTL shall be ready to receive all
            remaining data bytes from the ICC.
            */
            uint32_t transfer_len;

            if(session->len <= session->offset)
            {
                ret = ERR_ASYNC;
                break;
            }

            transfer_len = session->len - session->offset;
            ret = session->transfer(slot, session->data + session->offset, transfer_len);

            if(ret != NO_ERR)
            {
                break;
            }

            session->offset += transfer_len;

        }
        else if(procedure_byte == (INS ^ 0xFF))
        {
            /*
            	The next data byte shall be transferred by the
            TTL, or the TTL shall be ready to receive the next
            data byte from the ICC
            */
            if(session->len <= session->offset)
            {
                ret = ERR_ASYNC;
                break;
            }

            ret = session->transfer(slot, session->data + session->offset, 1);

            if(ret != NO_ERR)
            {
                break;
            }

            session->offset++;
        }
        else
        {
            ret = ERR_INVALID_SW;
            break;
        }
    }
    while(1);

    CharacterModule_TimerStop(slot->module);
    return ret;
}


static void Session_SetCommandHeader(session_t *session, uint8_t c_apdu[])
{
    memcpy(session->cmd, c_apdu, 4);
}

static void Session_SetCommandP3(session_t *session, uint8_t P3)
{
    session->cmd[4] = P3;
}


static uint8_t * get_response(uint8_t P3)
{
    static uint8_t GET_RESPONSE_COMMAND[5] = {0x00, 0xC0, 0x00, 0x00};

    GET_RESPONSE_COMMAND[4] = P3;
    return GET_RESPONSE_COMMAND;
}


static uint32_t convert_Le_to_value(uint8_t Le)
{
    return Le != 0 ? Le : 256;
}

/*****************************************************************************
 函 数 名  : get_Lc_Le
 功能描述  : 识别APDU类型
 输入参数  : uint8_t apdu_send[]
             uint32_t apdu_send_len
 输出参数  :
 返 回 值  :	apdu case 码
 调用函数  :
 被调函数  :

 修改历史      :
  1.日    期   : 2012年8月27日
    作    者   : Jason
    修改内容   : 新生成函数

*****************************************************************************/
static uint32_t check_apdu_case(uint8_t apdu[], uint32_t apdu_len)
{

    uint8_t C5 = apdu[4];

    if(apdu_len < 4)
    {
        //Invalid APDU
        return 0;
    }

    if(apdu_len == 4)
    {
        //Case 1
        return  1;
    }

    if(apdu_len == 5)
    {
        //Case 2
        return  2;
    }

    if(apdu_len == ((uint32_t)5 + C5))
    {
        //Case 3
        return  3;
    }

    if(apdu_len == ((uint32_t)5 + C5 + 1))
    {
        //Case 4
        return  4;
    }

    //Invalid APDU
    return 0;
}

static uint32_t Session_IsOut(uint32_t command_case)
{
    return command_case == 3 || command_case == 4;
}

static void Session_Init(session_t *session, uint8_t cmd[], uint8_t data[], uint32_t len)
{
    Session_SetCommandHeader(session, cmd);
    Session_SetCommandP3(session, cmd[4]);
    session->data = data;
    session->len = len;
    session->offset = 0;
}

static void Session_InInit(session_t *session, uint8_t cmd[], uint8_t data[])
{
    Session_Init(session, cmd, data,  convert_Le_to_value(cmd[4]));
    session->dir = SESSION_IN;
    session->transfer = transfer_in;
}

static void Session_OutInit(session_t *session, uint8_t cmd[], uint8_t data[])
{
    Session_Init(session, cmd, data, cmd[4]);
    session->dir = SESSION_OUT;
    session->transfer = transfer_out;
}

static void Session_GetResponseInit(session_t *session, uint8_t data[], uint8_t P3)
{
    uint8_t *cmd = get_response(P3);

    Session_InInit(session, cmd, data);
}

void InitWarningLRC(uint8_t sw[], uint8_t len);

static uint32_t is_warning_sw(uint8_t sw[])
{
	if(sw[0] == 0x90 && sw[1] == 0x00)
	{
		return 0;
	}
	
	if(sw[0] == 0x62 || sw[0] == 0x63)
	{
		return 1;
	}

	if((sw[0] & 0xF0) == 0x90)
	{
		return 1;
	}
	
	return 0;
}

static uint32_t Session_ProcessSW(session_t *session, uint8_t buf[], uint32_t command_case)
{
    uint8_t sw1 = session->sw[0];
    uint8_t sw2 = session->sw[1];
    uint32_t ret = 0;


    if(sw1 == 0x90 && sw2 == 0x00)
    {
        return ret;
    }

    switch(sw1)
    {
        case 0x61:
        {
        /*
            	Procedure bytes '61xx' instruct the TTL to issue a GET RESPONSE command to
            the ICC. P3 of the GET RESPONSE command header is set to ? 'xx'.
            */
            ret = 1;
            Session_GetResponseInit(session, buf, sw2);
        }
        break;

        case 0x6C:
        {
        /*
            	Procedure bytes '6Cxx' instruct the TTL  to immediately resend the previous
            command header setting P3 = 'xx'.
            */
            ret = 1;
            Session_SetCommandP3(session, sw2);
            session->len = convert_Le_to_value(sw2);
        }
        break;
        
        default :
        {
            if(is_warning_sw(session->sw))
            {
             /*        
			  If the ICC returned status as in step 3(b) that indicates a warning
			  ('62xx' or '63xx'), or which is app lication related ('9xxx' but not '9000'),
			  the TTL shall send a GET RESPONSE command with Le='00'.
                	  app lication related ('9xxx' but not '9000'),
                	the TTL shall send a GET RESPONSE command with Le='00'.
                */
                /*数据发送完成后,直接返回警告,发送LE='00'的GET RESPONSE command*/
                if((command_case == 4) 
                	//&& (session->dir == SESSION_OUT) 
                	&& (session->offset == session->len))
                {
	                ret = 1;
	                Session_GetResponseInit(session, buf, 0);
                }
            }
        }
    }

    return ret;
}


uint32_t CharacterProtocol_ExchangeApdu(slot_t *slot, uint8_t apdu_send[], uint32_t apdu_send_len, uint8_t apdu_receive[], uint32_t *apdu_receive_len)
{
    uint32_t ret = ERR_INVALID_APDU;
    uint32_t command_case;
    uint32_t r_len = 0;
    session_t session;
	uint8_t warning_sw[2] = {0};
	icc_t *icc = slot->icc;
	
    *apdu_receive_len = 0;
    command_case = check_apdu_case(apdu_send, apdu_send_len);

    if(command_case == 0)
    {
        return ERR_INVALID_APDU;
    }


    if(Session_IsOut(command_case))
    {
        Session_OutInit(&session, apdu_send, apdu_send + 5);
    }
    else
    {
        if(command_case == 1)
        {
            apdu_send[4] = 0;
        }

        Session_InInit(&session, apdu_send, apdu_receive);
    }

    do
    {

        //ret = CharacterProtocol_Session(slot, &session, command_case);
        ret = Session_Transmission(slot, &session);

        if(ret != NO_ERR)
        {
            return ret;
        }

        if(session.dir == SESSION_IN)
        {
            r_len += session.offset;
        }
        
		/*将ICC返回的第一个WARNING状态字作为最后的状态字返回*/

        if(is_warning_sw(session.sw) 
           && (warning_sw[0] == 0x00))
        {
			warning_sw[0] = session.sw[0];
			warning_sw[1] = session.sw[1];
        }
        
        if(icc->work_mode == 0)
        {
			if((session.sw[0] == 0x61) 
				|| (session.sw[0] == 0x6C))
			{
				break;
			}
        }
        
        ret = Session_ProcessSW(&session, apdu_receive + r_len, command_case);
        command_case = 2;
    }
    while(ret != 0);

	if(warning_sw[0] == 0x00)
	{
	    apdu_receive[r_len++] = session.sw[0];
	    apdu_receive[r_len++] = session.sw[1];
    }
    else
    {
	    apdu_receive[r_len++] = warning_sw[0];
	    apdu_receive[r_len++] = warning_sw[1];
    }
    
    *apdu_receive_len = r_len;
    return ret;
}

#if 0
extern slot_t Slot1;

void testCharacterProtocol(void)
{
    static uint8_t c_apdu[] =
    {
        //0x00, 0xA4, 0x04, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x03, 0x33
        0x00 , 0xa4  , 0x04 , 0x00 , 0x09 , 0xa0 , 0x00 , 0x00 , 0x03 , 0x97  , 0x42 , 0x54 , 0x46 , 0x59
    };
    static uint8_t r_apdu[256];

    static uint32_t r_len;
    //
    CharacterProtocol_ExchangeApdu(&Slot1, c_apdu, sizeof(c_apdu), r_apdu, &r_len);
}
#endif

